昨天練習寫了 firebase
的 database function
但要整進 TodoApp 必須要寫很多 callback 來處理
當 ref 取得完資料的時候會把結果用 callback [回乎] 的方式拿回來
像是
callback = (snapshot) => {
// handle snapshot
}
listRef.once('value', callback)
但是用一堆 callback
的方式會寫出像是波動拳的程式碼
doFirstAsync((data1) => {
doSecondAsync((data2) => {
doThirdAsync((data3) => {
doForthAsync((data4) => {
doMore((data5) => {
// 猴溜ken
}, failureCb)
}, failureCb)
}, failureCb)
}, failureCb)
}, failureCb)
為了避免寫出像 波動拳
的或稱 callback hell
好像又叫做 Pyramid of Doom
悲慘金字塔?
Promise 就誕生了
官方說 Promise 有三大保證
不如舊做法,一個 Promise 有這些保證:
- Callback 不會在當次的迴圈運行結束前呼叫。
- Callback 用 .then 添加,在非同步運算結束後呼叫,像前面那樣。
- 複 Callback 可以透過重複呼叫 .then 達成。
但 Promise 主要的立即好處是串連。
如果有興趣可以讀讀看官方怎麼寫的我覺得寫得非常好
https://developer.mozilla.org/zh-TW/docs/Web/JavaScript/Guide/Using_promises#%E4%B8%B2%E9%80%A3
今天有 3 個 function 要依序被呼叫就可以用 promise 這樣寫
const firstPromise = () => new Promise((resolve) => {
setTimeout(() => {
console.log('first promise completed');
resolve();
}, 300);
});
const secondPromise = () => new Promise((resolve) => {
setTimeout(() => {
console.log('second promise completed');
resolve();
}, 200);
});
const thirdPromise = () => new Promise((resolve) => {
setTimeout(() => {
console.log('3rd promise completed');
resolve();
}, 100);
});
firstPromise()
.then(secondPromise)
.then(thirdPromise)
.then(() => {
console.log('done');
});
而像這樣參數一致的 Promise function
官方很熱心地教我們可以這樣寫
[firstPromise, secondPromise, thirdPromise, () => { console.log('done'); }].reduce((p, f) => p.then(f), Promise.resolve());
這兩個都會依序輸出
first promise completed
second promise completed
3rd promise completed
done
也可以使用 組合
串連的方式,讓 function 可以被複用的 compose 寫法
let applyAsync = (acc,val) => acc.then(val);
let composeAsync = (...funcs) => x => funcs.reduce(applyAsync, Promise.resolve(x));
const applyAsync = (acc, val) => acc.then(val);
const composeAsync = (...funcs) => x => funcs.reduce(applyAsync, Promise.resolve(x));
const composed = composeAsync(firstPromise, secondPromise, thirdPromise);
composed().then(() => {
console.log('done');
});
基本上 delete 和 update 會回傳 promise
Receive a Promise
To know when your data is committed to the Firebase Realtime Database server, you can use a Promise. Both set() and update() can return a Promise you can use to know when the write is committed to the database.
但 list
就必須要用 callback 了,為了方便使用,將其改為回傳 Promise
const getList = () => {
const listRef = firebase.database().ref(KEY_TODOS).orderByChild('ts');
return new Promise((resolve, reject) => {
listRef.once('value', (snapshot) => {
const res = [];
snapshot.forEach((childSnapshot) => {
const childKey = childSnapshot.key;
const childData = childSnapshot.val();
res.push({
id: childKey,
...childData,
});
});
resolve(res);
});
});
};
現在來試試做一序列的操作吧
寫法如下:
getList().then((list) => {
console.log(list);
return writeUserData('測試 promise 資料', false);
})
.then(getList)
.then((list) => {
const lastItem = list[list.length - 1];
console.log('========');
return updateTodo(lastItem.id, {
...lastItem,
checked: true,
});
})
.then(() => {
console.log('updated');
return getList();
})
.then((list) => {
console.log('========');
console.log(list);
const lastItem = list[list.length - 1];
console.log('========');
console.log(`刪除${lastItem.id}`);
return deleteTodo(lastItem.id);
})
.then(getList)
.then((list) => {
console.log('========');
console.log(list);
});
成功的結果如下
取得列表
[ { id: '-LPoJS_3YqW7LkjAJMSF',
checked: false,
name: '第一個任務',
ts: 1540623554884 },
{ id: '-LPoJTDfsMGLsJYBrld2',
checked: false,
name: '第二個任務',
ts: 1540623557547 } ]
新增一筆資料:
[ { id: '-LPoJS_3YqW7LkjAJMSF',
checked: false,
name: '第一個任務',
ts: 1540623554884 },
{ id: '-LPoJTDfsMGLsJYBrld2',
checked: false,
name: '第二個任務',
ts: 1540623557547 },
{ id: '-LQ-8l6nBV_SFJh4seaQ',
checked: false,
name: '測試 promise 資料',
ts: 1540822078020 } ]
========
更新剛新增的資料
========
[ { id: '-LPoJS_3YqW7LkjAJMSF',
checked: false,
name: '第一個任務',
ts: 1540623554884 },
{ id: '-LPoJTDfsMGLsJYBrld2',
checked: false,
name: '第二個任務',
ts: 1540623557547 },
{ id: '-LQ-8l6nBV_SFJh4seaQ',
checked: true,
name: '測試 promise 資料',
ts: 1540822078020 } ]
========
刪除-LQ-8l6nBV_SFJh4seaQ
========
[ { id: '-LPoJS_3YqW7LkjAJMSF',
checked: false,
name: '第一個任務',
ts: 1540623554884 },
{ id: '-LPoJTDfsMGLsJYBrld2',
checked: false,
name: '第二個任務',
ts: 1540623557547 } ]
明天就來將這個 firebase service 改成 library 來讓 TodoList 使用吧!
babel-node src/service/promise_pratice.js